#include <cstdio>
#include <functional>
#include <string>
#include <map>
#include <algorithm> // for std::remove_if
#include <cctype>    // for iscntrl
#include "freertos/FreeRTOS.h"
#include "esp_http_server.h"
#include "esp_log.h"
#include "lwip/err.h"
#include "lwip/sockets.h"
#include "lwip/sys.h"
#include <lwip/netdb.h>
#include "mbedtls/md5.h"
#include "my_webserver.h"
#include <esp_heap_caps.h>

static const char *TAG = "WEBSERVER";
static const char *DNS_TAG = "DNS_SERVER";
#define Debug 0

WebServer::WebServer(int port, int maxUriHandlers)
{
    this->port = port;
    max_uri_handlers = maxUriHandlers;
    server = NULL;
}

esp_err_t WebServer::start()
{
    // 初始化配置
    httpd_config_t server_init_cfg = HTTPD_DEFAULT_CONFIG();
    server_init_cfg.server_port = port;                       // 设置服务器端口
    server_init_cfg.max_uri_handlers = max_uri_handlers;      // 设置最大 URI 处理数量
    server_init_cfg.uri_match_fn = &httpd_uri_match_wildcard; // set uri match wildcard so that we can use wildcard in uri and match the un registd uris
    server_init_cfg.stack_size = 8 * 1024;                    // 设置堆栈大小为 8KB
    server_init_cfg.lru_purge_enable = true;                  // 启用LRU清理
    server_init_cfg.max_open_sockets = 2;
    server_init_cfg.core_id = 0;

    // 启动服务器
    httpd_handle_t server = NULL;
    if (ESP_OK != httpd_start(&server, &server_init_cfg))
    {
        ESP_LOGE("WEBSERVER", "Failed to start server");
        this->server = NULL;
        return ESP_FAIL;
    }
    else
    {
        ESP_LOGI(TAG, "Server started on port %d ", port);
        this->server = server;
    }
    return ESP_OK;
}

esp_err_t WebServer::stop()
{
    if (server != NULL)
        return httpd_stop(server);
    else
        return ESP_OK;
}

esp_err_t WebServer::registUri(const char *uri, httpd_method_t method, esp_err_t (*handleFunction)(httpd_req_t *r), void *user_arg)
{
    if (server == NULL)
    {
        ESP_LOGE(TAG, "Server is not started");
        return ESP_FAIL;
    }

    // 创建 URI 配置结构
    httpd_uri_t cfg = {
        .uri = uri,
        .method = method,
        .handler = handleFunction,
        .user_ctx = user_arg,
    };
    if (ESP_OK != httpd_register_uri_handler(server, &cfg))
    {
        ESP_LOGE("WEBSERVER", "Failed to register URI handler %s", uri);
        return ESP_FAIL;
    }
    return ESP_OK;
}
esp_err_t WebServer::registNotFound(httpd_method_t method, esp_err_t (*handleFunction)(httpd_req_t *r), void *user_arg)
{
    if (server == NULL)
    {
        ESP_LOGE(TAG, "Server is not started");
        return ESP_FAIL;
    }
    // construct uri config

    httpd_uri_t cfg = {
        .uri = "/*",
        .method = method,
        .handler = handleFunction,
        .user_ctx = user_arg,
    };

    if (ESP_OK != httpd_register_uri_handler(server, &cfg))
    {
        ESP_LOGE("WEBSERVER", "Failed to register URI handler");
        return ESP_FAIL;
    }
    return ESP_OK;
}

esp_err_t WebServer::send(httpd_req_t *req, const char *senString)
{
    size_t len = strlen(senString);
    httpd_resp_set_type(req, "text/plain");
    return httpd_resp_send(req, senString, len);
}
esp_err_t WebServer::send(httpd_req_t *req, const std::string &senString)
{
    return send(req, senString.c_str());
}
// esp_err_t WebServer::send(httpd_req_t *req, FILE *file,  std::string type)
// {
//     // read file
//     if (file == nullptr)
//         return ESP_FAIL;
//     // 移动到文件末尾以确定文件大小
//     fseek(file, 0, SEEK_END);
//     long file_size = ftell(file);
//     rewind(file); // 回到文件的起始位置

//     if (file_size <= 0)
//         return ESP_FAIL; // 文件为空，直接返回空字符串

//     // 分配内存来存储文件内容
//     std::string result;
//     result.resize(file_size);

//     // 使用 fread 读取整个文件内容到字符串的内存中
//     size_t read_size = fread(&result[0], 1, file_size, file);
//     if (read_size != file_size)
//     {
//         ESP_LOGI(TAG,"READ FILE FAILED");
//         return ESP_FAIL;
//     }

//     //  send file
//     // 1.set mime type
//     httpd_resp_set_type(req, type.c_str());
//     // can set other head infos
//     // httpd_resp_set_hdr(req, "Content-Disposition", "attachment; filename=example.txt");
//     // 2.send infos
//     return httpd_resp_send(req, result.c_str(), read_size);
// }

/* After analyse the function fellow is faster than above in the situation
that send file .At the same time the following solution can provide faster
require speed for client ,because it can let the client load gradually rather
than load all file in same time .This give faster response speed for vister.*/
esp_err_t WebServer::send(httpd_req_t *req, FILE *file, const std::string &file_type)
{
    const size_t send_chunk_buffer_size = 1024;

    // read file
    if (file == nullptr)
        return ESP_FAIL;
    // 移动到文件末尾以确定文件大小
    fseek(file, 0, SEEK_END);
    long file_size = ftell(file);
    rewind(file); // 回到文件的起始位置

    // before send file set some infos
    httpd_resp_set_type(req, file_type.c_str());
    httpd_resp_set_status(req, "200 OK");

    // send file
    char buffer[send_chunk_buffer_size];
    while (file_size > 0)
    {
        // in last read , may be the file size is less than send_chunk_buffer_size
        //  so we need to clear the buffer for preventing send infos that we not need

        // if (file_size < send_chunk_buffer_size)
        //     memset(buffer, 0, send_chunk_buffer_size);

        size_t read_size = fread(buffer, 1, send_chunk_buffer_size, file);
        httpd_resp_send_chunk(req, buffer, read_size);
        file_size -= read_size;
    }
    // call the httpd_resp_send_chunk to finish the send
    return httpd_resp_send_chunk(req, NULL, 0);
}

std::string WebServer::uri(httpd_req_t *req) // This function is for the non-register uir to get uri for following handle
{
    return std::string(req->uri);
}

void WebServer::removeInvisibleChars(std::string &str)
{
    str.erase(std::remove_if(str.begin(), str.end(),
                             [](unsigned char c)
                             {
                                 return !(std::isprint(c) && c != ' ');
                             }),
              str.end());
}

std::map<std::string, std::string> WebServer::parseFormData(std::string &data)
{
    removeInvisibleChars(data);
    std::map<std::string, std::string> result;
    size_t pos = 0;
    while ((pos = data.find("name=\"", pos)) != std::string::npos)
    {
        // 提取name
        size_t name_start = pos + 6; // "name=\"" 的长度是6
        size_t name_end = data.find("\"", name_start);
        std::string name = data.substr(name_start, name_end - name_start);

        // 提取value (在name后面的两个换行之后)
        size_t value_start = name_end + 1; /*jump the " */

        if (value_start != std::string::npos)
        {
            size_t value_end = data.find("-", value_start);
            std::string value = data.substr(value_start, value_end - value_start);
            result[name] = value;
        }

        pos = name_end;
    }

    return result;
}
std::string WebServer::arg(httpd_req_t *req, std::string key)
{
    if (req->method == HTTP_GET)
    {
        std::string value;
        std::string uri(req->uri);
        size_t pos = uri.find("?");
        if (pos != -1)
            uri = uri.substr(pos + 1);
        else
            return "";

        pos = uri.find(key);

        if (pos != -1)
        {

            uri = uri.substr(pos);

            pos = uri.find("=");
            if (pos != -1)
                uri = uri.substr(pos + 1);
            else
                return "";

            pos = uri.find("&");
            if (pos != -1)
                value = uri.substr(0, pos);
            else
                value = uri;
        }
        else
            value = "";

        return value;
    }

    
    return "";
}
std::string WebServer::getBody(httpd_req_t *req)
{
    char *body = (char *)heap_caps_malloc(req->content_len, MALLOC_CAP_DEFAULT);
    if (body == nullptr)
    {
        ESP_LOGE(TAG, "Failed to allocate memory for body");
        return "";
    }
    memset(body, 0, req->content_len);

    int err = httpd_req_recv(req, body, req->content_len);
    if (err <= 0)
    { /* 返回 0 表示连接已关闭 */
        /* 检查是否超时 */
        if (err == HTTPD_SOCK_ERR_TIMEOUT)
        {
            /* 如果是超时，可以调用 httpd_req_recv() 重试
             * 简单起见，这里我们直接
             * 响应 HTTP 408（请求超时）错误给客户端 */
            httpd_resp_send_408(req);
        }
        /* 如果发生了错误，返回 ESP_FAIL 可以确保
         * 底层套接字被关闭 */
        ESP_LOGE(TAG, "Failed to receive data from client");
        free(body);
        return "";
    }

    std::string ret = std::string(body, req->content_len);
    free(body);

    return ret;
}

esp_err_t WebServer::setHeader(httpd_req_t *r, const char *field, const char *value)
{
    return httpd_resp_set_hdr(r, field, value);
}

int DNSServer::create_udp_socket(int port)
{
    struct sockaddr_in saddr = {0};
    int sock = -1;
    int err = 0;

    sock = socket(PF_INET, SOCK_DGRAM, IPPROTO_IP);
    if (sock < 0)
    {
        ESP_LOGE(TAG, "Failed to create socket. Error %d", errno);
        return -1;
    }

    // Bind the socket to any address
    saddr.sin_family = PF_INET;
    saddr.sin_port = htons(port);
    saddr.sin_addr.s_addr = htonl(INADDR_ANY);
    err = bind(sock, (struct sockaddr *)&saddr, sizeof(struct sockaddr_in));
    if (err < 0)
    {
        ESP_LOGE(TAG, "Failed to bind socket. Error %d", errno);
        goto err;
    }

    // All set, socket is configured for sending and receiving
    return sock;

err:
    close(sock);
    return -1;
}

void DNSServer::init()
{
    sock = create_udp_socket(53);
    ESP_LOGI(DNS_TAG, "DNS server start ...");

    if (sock < 0)
    {
        ESP_LOGE(DNS_TAG, "Failed to create IPv4 multicast socket");
        return;
    }
    auto task = [](void *p)
    {
        DNSServer *self = static_cast<DNSServer *>(p);
        while (self->avaliable)
        {
            uint8_t data[128];
            struct sockaddr_in client = {0};
            socklen_t client_len = sizeof(struct sockaddr_in);
            int len = recvfrom(self->sock, data, 100, 0, (struct sockaddr *)&client, &client_len); // 阻塞式

            if (self->avaliable == false)
                break;

            if ((len < 0) || (len > 100))
            {
                ESP_LOGE(DNS_TAG, "recvfrom error\n");
                continue;
            }

#if Debug
            printf("DNS request:");
            for (i = 0x4; i < len; i++)
            {
                if ((data[i] >= 'a' && data[i] <= 'z') || (data[i] >= 'A' && data[i] <= 'Z') || (data[i] >= '0' && data[i] <= '9'))
                    printf("%c", data[i]);
                else
                    printf("_");
            }
            printf("\r\n");
#endif
            // printf("%d\r\n",esp_get_free_heap_size()); //打印系统可用内存

            // // 过滤掉一些乱糟糟的域名
            // if (strstr((const char *)data + 0xc, "taobao") ||
            //     strstr((const char *)data + 0xc, "qq") ||
            //     strstr((const char *)data + 0xc, "sogou") ||
            //     strstr((const char *)data + 0xc, "amap") ||
            //     strstr((const char *)data + 0xc, "alipay") ||
            //     strstr((const char *)data + 0xc, "youku") ||
            //     strstr((const char *)data + 0xc, "iqiyi") ||
            //     strstr((const char *)data + 0xc, "baidu"))
            // {
            //     continue;
            // }

            data[2] |= 0x80;
            data[3] |= 0x80;
            data[7] = 1;

            data[len++] = 0xc0;
            data[len++] = 0x0c;

            data[len++] = 0x00;
            data[len++] = 0x01;
            data[len++] = 0x00;
            data[len++] = 0x01;

            data[len++] = 0x00;
            data[len++] = 0x00;
            data[len++] = 0x00;
            data[len++] = 0x0A;

            data[len++] = 0x00;
            data[len++] = 0x04;

            data[len++] = self->ip[0];
            data[len++] = self->ip[1];
            data[len++] = self->ip[2];
            data[len++] = self->ip[3];

            // data[len++] = 192; // self->ip[0];
            // data[len++] = 168; // self->ip[1];
            // data[len++] = 4;   // self->ip[2];
            // data[len++] = 1;   // self->ip[3];

#if Debug
            for (int i = 0; i < len; i++)
                printf("%d", data[i]);

            /*打印客户端地址和端口号*/
            // inet_ntop(AF_INET, &client.sin_addr, (char *)data, sizeof(data));
            // printf("client IP is %s, port is %d\n", data, ntohs(client.sin_port));
#endif

            sendto(self->sock, data, len, 0, (struct sockaddr *)&client, client_len);

            vTaskDelay(20 / portTICK_PERIOD_MS);
        }

        vTaskDelete(NULL);
    };
    avaliable = true;
    // xTaskCreatePinnedToCore(task, "dns_task", 4096, this, 5, NULL, 0);
}
void DNSServer::poll()
{
    if (avaliable)
    {
        uint8_t data[128];
        struct sockaddr_in client = {0};
        socklen_t client_len = sizeof(struct sockaddr_in);
        int len = recvfrom(sock, data, 100, 0, (struct sockaddr *)&client, &client_len); // 阻塞式

        if (avaliable == false)
            return;

        if ((len < 0) || (len > 100))
        {
            ESP_LOGE(DNS_TAG, "recvfrom error\n");
            return;
        }

#if Debug
        printf("DNS request:");
        for (i = 0x4; i < len; i++)
        {
            if ((data[i] >= 'a' && data[i] <= 'z') || (data[i] >= 'A' && data[i] <= 'Z') || (data[i] >= '0' && data[i] <= '9'))
                printf("%c", data[i]);
            else
                printf("_");
        }
        printf("\r\n");
#endif
        // printf("%d\r\n",esp_get_free_heap_size()); //打印系统可用内存

        // // 过滤掉一些乱糟糟的域名
        // if (strstr((const char *)data + 0xc, "taobao") ||
        //     strstr((const char *)data + 0xc, "qq") ||
        //     strstr((const char *)data + 0xc, "sogou") ||
        //     strstr((const char *)data + 0xc, "amap") ||
        //     strstr((const char *)data + 0xc, "alipay") ||
        //     strstr((const char *)data + 0xc, "youku") ||
        //     strstr((const char *)data + 0xc, "iqiyi") ||
        //     strstr((const char *)data + 0xc, "baidu"))
        // {
        //     continue;
        // }

        data[2] |= 0x80;
        data[3] |= 0x80;
        data[7] = 1;

        data[len++] = 0xc0;
        data[len++] = 0x0c;

        data[len++] = 0x00;
        data[len++] = 0x01;
        data[len++] = 0x00;
        data[len++] = 0x01;

        data[len++] = 0x00;
        data[len++] = 0x00;
        data[len++] = 0x00;
        data[len++] = 0x0A;

        data[len++] = 0x00;
        data[len++] = 0x04;

        data[len++] = ip[0];
        data[len++] = ip[1];
        data[len++] = ip[2];
        data[len++] = ip[3];

        // data[len++] = 192; // ip[0];
        // data[len++] = 168; // ip[1];
        // data[len++] = 4;   // ip[2];
        // data[len++] = 1;   // ip[3];

#if Debug
        for (int i = 0; i < len; i++)
            printf("%d", data[i]);

        /*打印客户端地址和端口号*/
        // inet_ntop(AF_INET, &client.sin_addr, (char *)data, sizeof(data));
        // printf("client IP is %s, port is %d\n", data, ntohs(client.sin_port));
#endif

        sendto(sock, data, len, 0, (struct sockaddr *)&client, client_len);
    }
}
void DNSServer::stop()
{
    avaliable = false;
    ESP_LOGW(TAG, "DNS　server stop ...");
    shutdown(sock, 0);
    close(sock);
}

bool endsWith(const std::string &str, const std::string &target)
{
    if (str.length() < target.length())
    {
        return false;
    }
    return str.substr(str.length() - target.length()) == target;
}

std::string getContentType(std::string filename)
{
    if (endsWith(filename, ".htm"))
        return "text/html";
    else if (endsWith(filename, ".html"))
        return "text/html";
    else if (endsWith(filename, ".css"))
        return "text/css";
    else if (endsWith(filename, ".js"))
        return "application/javascript";
    else if (endsWith(filename, ".png"))
        return "image/png";
    else if (endsWith(filename, ".gif"))
        return "image/gif";
    else if (endsWith(filename, ".jpg"))
        return "image/jpeg";
    else if (endsWith(filename, ".ico"))
        return "image/x-icon";
    else if (endsWith(filename, ".xml"))
        return "text/xml";
    else if (endsWith(filename, ".pdf"))
        return "application/x-pdf";
    else if (endsWith(filename, ".zip"))
        return "application/x-zip";
    else if (endsWith(filename, ".gz"))
        return "application/x-gzip";
    else if (endsWith(filename, ".mp3"))
        return "audio/mpeg";
    return "text/plain";
}
